package org.loong.crypto.core.algorithm;

import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.loong.crypto.core.Algorithm;
import org.loong.crypto.core.AlgorithmFamily;

/**
 * The MessageDigest algorithm.
 */
public final class MessageDigestAlgorithm extends Algorithm {

    /**
     * Digest using MD2 algorithm.
     */
    public static final MessageDigestAlgorithm MD2 = new MessageDigestAlgorithm("MD2", PKCSObjectIdentifiers.md2);

    /**
     * Digest using MD2 algorithm.
     */
    public static final MessageDigestAlgorithm MD4 = new MessageDigestAlgorithm("MD4", PKCSObjectIdentifiers.md4);

    /**
     * Digest using MD5 algorithm.
     */
    public static final MessageDigestAlgorithm MD5 = new MessageDigestAlgorithm("MD5", PKCSObjectIdentifiers.md5);

    /**
     * Digest using SHA-1 algorithm.
     */
    public static final MessageDigestAlgorithm SHA1 = new MessageDigestAlgorithm("SHA-1", OIWObjectIdentifiers.idSHA1);

    /**
     * Digest using SHA-224 algorithm.
     */
    public static final MessageDigestAlgorithm SHA224 = new MessageDigestAlgorithm("SHA-224", NISTObjectIdentifiers.id_sha224);

    /**
     * Digest using SHA-256 algorithm.
     */
    public static final MessageDigestAlgorithm SHA256 = new MessageDigestAlgorithm("SHA-256", NISTObjectIdentifiers.id_sha256);

    /**
     * Digest using SHA-384 algorithm.
     */
    public static final MessageDigestAlgorithm SHA384 = new MessageDigestAlgorithm("SHA-384", NISTObjectIdentifiers.id_sha384);

    /**
     * Digest using SHA-512 algorithm.
     */
    public static final MessageDigestAlgorithm SHA512 = new MessageDigestAlgorithm("SHA-512", NISTObjectIdentifiers.id_sha512);

    /**
     * Digest using SM3 algorithm.
     */
    public static final MessageDigestAlgorithm SM3 = new MessageDigestAlgorithm("SM3", GMObjectIdentifiers.sm3);

    /**
     * The supported message digest algorithms.
     */
    public static final Map<String, MessageDigestAlgorithm> SUPPORTED_ALGORITHMS;

    static {
        Map<String, MessageDigestAlgorithm> algs = new LinkedHashMap<>();
        // MD
        algs.put(MD2.getName(), MD2);
        algs.put(MD4.getName(), MD4);
        algs.put(MD5.getName(), MD5);

        // SHA
        algs.put(SHA1.getName(), SHA1);
        algs.put(SHA224.getName(), SHA224);
        algs.put(SHA256.getName(), SHA256);
        algs.put(SHA384.getName(), SHA384);
        algs.put(SHA512.getName(), SHA512);

        // SM3
        algs.put(SM3.getName(), SM3);
        SUPPORTED_ALGORITHMS = Collections.unmodifiableMap(algs);
    }

    /**
     * Digest algorithm family.
     */
    public static final class Family extends AlgorithmFamily<MessageDigestAlgorithm> {

        private static final long serialVersionUID = 1L;

        /**
         * MD2/5 digest algorithms.
         */
        public static final Family MD = new Family(MD2, MD4, MD5);

        /**
         * SHA/1/224/256/384/512 digest algorithms.
         */
        public static final Family SHA = new Family(SHA1, SHA224, SHA256, SHA384, SHA512);

        /**
         * Creates a new digest algorithm family.
         *
         * @param algs The encryption algorithms of the family. Must not be {@code null}.
         */
        public Family(final MessageDigestAlgorithm... algs) {
            super(algs);
        }
    }

    /**
     * Creates a new digest algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     * @param oid the algorithm object identifier. Must not be {@code null}.
     */
    public MessageDigestAlgorithm(final String name, final ASN1ObjectIdentifier oid) {
        super(name, oid);
    }

    /**
     * Parses a digest algorithm from the specified string.
     *
     * @param name the algorithm name. Must not be {@code null}.
     * @return the digest algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static MessageDigestAlgorithm find(final String name) throws NoSuchAlgorithmException {
        MessageDigestAlgorithm algorithm = SUPPORTED_ALGORITHMS.get(name);
        if (algorithm == null) {
            throw new NoSuchAlgorithmException("Unknown digest algorithm type requested: " + name);
        }

        return algorithm;
    }

    /**
     * Find a digest algorithm from the specified string.
     *
     * @param algorithmId the digest algorithm id. Must not be {@code null}.
     * @return the digest algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static MessageDigestAlgorithm findByAlgorithmId(final AlgorithmIdentifier algorithmId) throws NoSuchAlgorithmException {
        final ASN1ObjectIdentifier oid = algorithmId.getAlgorithm();
        return SUPPORTED_ALGORITHMS.values().stream().filter(algorithm -> Objects.equals(algorithm.getOID(), oid)).findFirst()
                .orElseThrow(() -> new NoSuchAlgorithmException("Unknown digest algorithm type requested: " + oid));
    }
}
